#ifndef __RCC_H
#define __RCC_H

#include "./compat.h"

namespace stm32f1::rcc {

enum class Bus : uint8_t {
    AHB,
    APB1,
    APB2,
};

inline constexpr uint32_t AHB_FREQ = 72000000;
inline constexpr uint32_t APB1_FREQ = 36000000;
inline constexpr uint32_t APB2_FREQ = 72000000;

inline void use_swd() {
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    AFIO->MAPR = 2 << 24;
}

inline void adc_prescaler_init() { RCC->CFGR |= RCC_CFGR_ADCPRE_DIV8; }

template <Bus BUS, uint32_t FIELD, uint32_t FREQ> struct PeriphBus {

    static constexpr uint32_t FREQ_VAL = FREQ;
    static constexpr Bus BUS_VAL = BUS;
    static constexpr uint32_t FIELD_VAL = FIELD;

    static inline void rcc_enable() {
        if constexpr (BUS == Bus::AHB) {
            RCC->AHBENR |= FIELD;
        } else if constexpr (BUS == Bus::APB1) {
            RCC->APB1ENR |= FIELD;
        } else if constexpr (BUS == Bus::APB2) {
            RCC->APB2ENR |= FIELD;
        }
    }
};

namespace inner {

template <Bus BUS, typename PERIPH, typename... Ps>
constexpr uint32_t sum_fields() {
    if constexpr (BUS == PERIPH::BUS_VAL) {
        if constexpr (sizeof...(Ps) == 0) {
            return PERIPH::FIELD_VAL;
        } else {
            return PERIPH::FIELD_VAL | sum_fields<BUS, Ps...>();
        }
    } else {
        if constexpr (sizeof...(Ps) == 0) {
            return 0;
        } else {
            return sum_fields<BUS, Ps...>();
        }
    }
}

} // namespace inner

template <typename... Ps> inline void enable() {

    constexpr uint32_t AHB_SUM = inner::sum_fields<Bus::AHB, Ps...>();
    if constexpr (AHB_SUM != 0) {
        RCC->AHBENR |= AHB_SUM;
    }

    constexpr uint32_t APB1_SUM = inner::sum_fields<Bus::APB1, Ps...>();
    if constexpr (APB1_SUM != 0) {
        RCC->APB1ENR |= APB1_SUM;
    }

    constexpr uint32_t APB2_SUM = inner::sum_fields<Bus::APB2, Ps...>();
    if constexpr (APB2_SUM != 0) {
        RCC->APB2ENR |= APB2_SUM;
    }
}

template <uint32_t FIELD> using AHBBus = PeriphBus<Bus::AHB, FIELD, AHB_FREQ>;

template <uint32_t FIELD>
using APB1Bus = PeriphBus<Bus::APB1, FIELD, APB1_FREQ>;

template <uint32_t FIELD>
using APB2Bus = PeriphBus<Bus::APB2, FIELD, APB2_FREQ>;

using PeriphCan1 = APB1Bus<RCC_APB1ENR_CAN1EN>;

} // namespace stm32f1::rcc

#endif
